#include <iostream>
#include "Box.h"
#include <cstring>

using namespace std;

/*
 * 面向对象(oop):
 * 类: 现实世界的事物所具有的共性就是每个事物都具有自身的属性和行为，比如一个学生有姓名、性别，会吃饭、打游戏。基于oop的思想，C++提供了类这个概念，
 *     可以将某一类事物的所有属性和行为封装在一个class中。
 * 对象:类对于事物的描述是抽象的(因为只是描述而不知道这个事物具体是什么样的)，所以类只是对某一类事物的一个描述而已。实际应用中我们需要操作
 *     某类事物中一个或者多个具体的事物。那么这个具体的事物我们就称之为对象。
 * 类是抽象的，对象是具象的。对象是怎么来的呢？由类实例化而来。因此我们也说通过一个类实例化一个对象。
 *
 */
int main() {
    /*
        类的定义(属性->变量  行为->函数/方法):
        class 类名(一般保证首字母大写)
        {
            访问控制符:
                成员变量 //属性;
                成员函数 //方法
        };
        访问控制符有三种: public、private、protected
     */
    //示例: 定义一个类描述一种动物
    class Animal {
    public:
        //属性
        char name[32]; //名字
        int age; //年龄

        //行为(方法)
        void shout(char *voice) //描述动物叫的行为，voice为叫的声音
        {
            //在成员函数中直接访问成员变量
            cout << name << voice << endl;
        }
    };

    //类的基本使用:
    // 1、实例化普通对——类名  对象名称
    Animal cat; //构造一个Animal类的实例化对象，cat就是一个Animal的实例化对象
    // 2、使用数组实例化多个普通对象——类名  数组名[数组长度];
    Animal cats[10]; //实例化10个CAnimal的实例化对象
    // 3、定义一个指针变量——类名  *对象名称;
    Animal *catPoint = &cat; //*cat就是一个指针变量，可以指向一个CAnimal的实例化对象
    //TIPS: 指针变量不是类的实例化对象！本质是个指针！！也就是说定义一个类类型的指针变量根本就没有实例化一个对象
    //TIPS: 当然对象的实例化方法远不止以上两种方法，更复杂的实例化对象的方法在后续再说。

    //成员变量和成员函数的访问:
    // 1、普通对象——使用.符号访问成员变量和成员函数
    cat.age = 1;
    cout << cat.age << endl;
    // 2、指针变量——使用->访问成员变量和成员函数
    catPoint->age = 2;
    cout << catPoint->age << endl;
    // 总结：对象中成员变量和成员函数的访问与结构体访问成员变量的方法类似。

    //类成员的访问控制:
    //1、在C++中可以给成员变量和成员函数定义访问级别。
    // · public 公有的，修饰成员变量和成员函数可以在类的内部和类的外部被访问
    // · private 私有的，修饰成员变量和成员函数只能在类的内部被访问
    // · protected  被保护的，修饰成员变量和函数只能在类的内部被访问
    class Teacher {
    public:
        char name[20];
    private:
        int age;
    protected:
        char sex;
    public:
        void setAge(int age) {
            //形参的合法性校验
            if (age > 200 || age < 0) {
                cout << "设置的年龄不合理" << endl;
                return;
            }
            this->age = age;
        }

        int getAge() {
            return age;
        }
    } teacher;
    teacher.name;
    //error: 'int main()::Teacher::age' is private within this context
//    teacher.age;
    //error: 'char main()::Teacher::sex' is protected within this context
//    teacher.sex;
    //为什么需要访问级别？
    // 举例: t.age = -10000;
    // 显然这个代码对age属性的赋值是不合理的，因此为了保障某些属性的"安全"，我们常常定义为private，然后对象只能通过类中的public方法访问private属性。
    teacher.setAge(30);
    cout << teacher.getAge() << endl;
    teacher.setAge(-10000);
    cout << teacher.getAge() << endl;

    //示例: 求某一个立方体的体积。要求将类的声明和实现分开
    Box box;
    box.set_h(10);
    cout << box.get_h() << endl;

    //对象的构造
    //前言:创建一个对象时，常常需要作某些初始化的工作，例如对数据成员赋初值。但是类的数据成员不应该在声明类时初始化(因为类是抽象的，所以不应该这么做)。
    //为了解决这个问题，C++编译器提供了构造函数(constructor)来处理对象的初始化。
    //  构造函数是一种特殊的成员函数，与其他成员函数不同，不需要用户来调用它，而是在建立对象时自动执行。
    // 构造函数定义:
    // 1、C++中的类可以定义与类名相同的特殊成员函数，这种与类名相同的成员函数叫做构造函数
    // 2、构造函数在定义时可以有参数，也可以没有参数
    // 3、没有任何返回类型的声明
    //TIPS:因为类的定义是抽象的，它并不会在内存中占用空间，只有在创建对象时才会被分配内存空间。
    class Student {
    public:
        //无参构造函数: 默认的构造函数。像是之前的类定义中若程序员没有自己显式地定义，则编译器也会自动生成。
        // TIPS: 如果在类中实现了带参数的构造函数，一定要实现一个无参的构造函数(因为不给你自动生成了)，因为如果在构造对象时不带参数将无法找到无参的构造函数导致编译失败。
        Student() { cout << "Student的无参构造器被调用" << endl; }

        //带参构造函数: 构造对象的时候传递一个参数时会自动调用。函数的设计同函数重载。
        Student(int _age) {
            cout << "Student的带参构造器(_age)被调用" << endl;
            age = _age;
        }

        //带参构造函数: 同理，只是多了个参数
        Student(char *_name, int _age) {
            cout << "Student的带参构造器(*_name,_age)被调用" << endl;
            age = _age;
            memset(name, 0, sizeof(name));
            strcpy(name, _name);
        }

        char name[20];
        int age;
        char sex;

    };
    //在实例化对象时无参构造会由编译器主动调用
    Student a, b;
    Student x[3];
    //主动使用有参构造函数
    Student a2(10);
    cout << a2.age << endl;
    Student a3("小黄", 3);
    cout << a3.name << endl;

    //扩展知识:
    class Student2 {
    public:
        //构造函数的高级用法:初始化成员列表
        // ·由逗号分隔的初始化列表组成（前面带冒号）
        // ·位于参数列表的右括号之后、函数体左括号之前
        // ·如果数据成员的名称为data，并需要将它初始化为val，则初始化器为data(val)
        // TIPS:什么时候必须用呢，当成员变量包含引用或者只读变量时使用。
        // TIPS:如果不提供初始值直接写data()，则会为成员变量赋予默认的初始值——比如整形会赋值0，字符类型赋值''，字符串类型赋值""
        Student2(int _age, char _sex) : name(), age(_age), sex(_sex), refer(age), sum(100) {
            cout << "Student2的带参构造器(_age,_sex)被调用" << endl;
        }

        //error: uninitialized reference member in 'int&'
        //error: uninitialized const member in 'const int'
        //可以看到无参构造器报错了，为什么呢，因为引用和只读常量是必须初始化的，而若果调用此构造器，则这些成员变量都尚未初始化没法使用。
//        Student2() {
//            // 初始化成员列表与在此处进行赋值是两种不同的概念，这样子的写法是对引用进行赋值的意思。
//            // TIPS:初始化发生在为对象以及成员变量分配内存空间时(实例化对象时)，赋值发生在分配空间后(对象已定义)
//            // 所以若成员变量存在引用或者只读变量时必须使用初始化成员列表
//            refer = age;
//            sum = 999;
//        }

        Student2(char _sex) : name(), sex(_sex), refer(age), sum(100) {
            cout << "Student2的带参构造器(_sex)被调用" << endl;
            cout << "当前age的值: " << age << endl;
            //这里不报错的缘故是因为引用已经初始化了，所以本质上此语句作用是age=12
            refer = 12;
        }

        //当成员变量包含其他类的时候，可以在初始化成员变量时直接绕过默认调用无参构造直接调用有参构造(避免某些类没有提供无参构造的情况)
        Student2() : refer(age), sum(100), student(age) {
            cout << "Student2的无参构造器被调用" << endl;
        }

        /*
            总结: 初始化成员列表什么时候必须要使用呢?
            1、成员变量是引用的时候
            2、成员变量被const修饰的时候
            3、成员变量是另外一个类的实例化对象，且对应的类中没有实现无参构造函数的时候
         */

        char name[20];
        //TIPS:C++11标准扩展语法，可以为通过这个类构造的所有对象设置一个初始值——也可以避免未初始化的问题。
        // 但是基于类是抽象的，且是扩展语法，所以一般不建议这样写(根据实际工作环境而定)。
        int age = 99;
        char sex;
        int &refer;
        const int sum;
        //类里面也是可以包含类这个"数据类型"的。
        Student student;
    };
    Student2 a4(23, 'g');
    cout << a4.age << endl;
    cout << a4.sex << endl;
    cout << a4.refer << endl;
    cout << a4.sum << endl;
    Student2 a5('m');
    cout << a5.age << endl;
    cout << a5.sex << endl;
    cout << a5.refer << endl;
    cout << a5.sum << endl;
    Student2 a6;


    /*
        总结:
        1）构造一个对象一定会自动调用一个构造函数
        2）如果一个类中没有实现默认构造函数，编译器会自动生成一个，前提是没有实现带参数的构造函数
        3）如果一个类中实现了带参数的构造函数，一定要实现一个无参的构造函数，因为如果在构造对象时不带参数将无法找到无参的构造函数导致编译失败
        4）构造函数可以有多个，根据构造对象时所传递的参数，会自动调用对应的构造函数
        5）类不会占用程序的内存空间，对象才会占用程序的内存空间
     */


    return 0;
}

/*
    1、面向过程程序设计：数据结构 + 算
    ·用户需求简单而固定
    特点：
    ·分析解决问题所需要的步骤
    ·利用函数实现各个步骤
    ·依次调用函数解决问题
    问题：
    ·软件可重用性差
    ·软件可维护性差

    2、面向对象程序设计：由现实世界建立软件模型
    ·属性：静态特征，可以用某种数据来描述
    ·方法：动态特征，对象所表现的行为或具有的功能
    将现实世界中的事物直接映射到程序中，可直接满足用户需求
    特点：
    ·直接分析用户需求中涉及的各个实体
    ·在代码中描述现实世界中的实体
    ·在代码中关联各个实体协同工作解决问题
    优势：
    ·构建的软件能够适应用户需求的不断变化

    3、面向对象三大特征
    ·封装
        ·把变量（属性）和函数（操作）合成一个整体，封装在一个类中
        ·尽可能隐蔽对象的内部细节。对外形成一个边界（或者说一道屏障），只保留有限的对外接口使之与外部发生联系
        ·对变量和函数进行访问控制，保证数据的安全性
    ·继承
    ·多态
 */

